package com.kexin.common.utils;

import com.alibaba.fastjson.JSONArray;
import com.alibaba.fastjson.JSONObject;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.codec.digest.DigestUtils;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.tuple.Pair;

import javax.crypto.Cipher;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.SecretKeySpec;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
import java.util.stream.Collectors;

/**
 * 验签
 */
@SuppressWarnings("restriction")
@Slf4j
public class PDSignUtil {

    public static final String SIGNATURE = "signature";

    private static Set<Pair<String, Object>> parseValue(String key, Object value) {
        Set<Pair<String, Object>> set = new HashSet<>();
        if (value == null || SIGNATURE.equalsIgnoreCase(key)) {
            return set;
        }

        if (value instanceof JSONArray) {
            JSONArray tmp = (JSONArray) value;
            if (tmp.isEmpty()) {
                return set;
            }
            Object element = tmp.get(0);
            if (element instanceof JSONArray || element instanceof JSONObject) {
                //list结构非空, 取第一个元素
                set.addAll(parseValue(key, element));
            }
        } else if (value instanceof JSONObject) {
            JSONObject tmp = (JSONObject) value;
            for (String k : tmp.keySet()) {
                set.addAll(parseValue(k, tmp.get(k)));
            }
        } else {
            set.add(Pair.of(key, value.toString()));
        }

        return set;
    }

    /**
     * 加签
     * @param map 请求的参数Map
     * @param appKey 签名key
     * @param appSecret 签名secret
     * @return
     */
    public static String sign(Map<String,Object> map, String appKey, String appSecret) {
        if(map == null || map.isEmpty()) {
            throw new IllegalArgumentException("参数待签名数据不能为空");
        }
        JSONObject jsonObject = new JSONObject(map);
        return sign(jsonObject, appKey, appSecret);
    }

    /**
     * 加签
     *
     * @param signJsonData 请求的json字符串
     * @param appKey 签名key
     * @param appSecret 签名secret
     * @return
     */
    public static String sign(String signJsonData, String appKey, String appSecret) {
        
        JSONObject jsonObject = JSONObject.parseObject(signJsonData);
        if (jsonObject == null) {
            throw new IllegalArgumentException("参数待签名数据不能为空");
        }
        return sign(jsonObject, appKey, appSecret);
    }

    /**
     * 加签
     *
     * @param jsonObject 请求的json对象
     * @param appKey 签名key
     * @param appSecret 签名secret
     * @return
     */
    public static String sign(JSONObject jsonObject, String appKey, String appSecret) {
        String sign = null;

        if (jsonObject == null) {
            throw new IllegalArgumentException("参数待签名数据不能为空");
        }
        try {
            Set<Pair<String, Object>> fieldList = parseValue(null, jsonObject);
            String paramList = fieldList.stream()
                    .sorted((a, b) -> a.getKey().compareToIgnoreCase(b.getKey()))
                    .map(e -> String.format("%s%s", e.getKey(), e.getValue()))
                    .collect(Collectors.joining());

            String str = appKey + paramList + appSecret;
            log.info("加密前明文: {}", str);
            sign = DigestUtils.sha1Hex(str).toUpperCase();
        } catch (Exception e) {
            throw new RuntimeException("签名异常", e);
        }

        return sign;
    }

    /**
     * 验签
     *
     * @param map 请求的参数map
     * @param sign 加签字符串
     * @param appKey 签名key
     * @param appSecret 签名secret
     * @return
     */
    public static boolean checkSign(Map<String,Object> map, String sign, String appKey, String appSecret) {
        boolean result = false;
        String resign = sign(map, appKey, appSecret);
        log.info("待验证签名：" + resign);
        log.info("加签后签名：" + sign);
        if (StringUtils.isNotBlank(sign) && sign.equals(resign)) {
            result = true;
            log.info("验证签名成功！");
        } else {
            log.error("验证签名失败！");
        }
        return result;
    }

    /**
     * 验签
     *
     * @param sign 加签字符串
     * @param json 请求的json字符串
     * @param appKey 签名key
     * @param appSecret 签名secret
     * @return
     */
    public static boolean checkSign(String json, String sign, String appKey, String appSecret) {
        boolean result = false;
        String resign = sign(json, appKey, appSecret);
        log.info("待验证签名：" + resign);
        log.info("加签后签名：" + sign);
        if (StringUtils.isNotBlank(sign) && sign.equals(resign)) {
            result = true;
            log.info("验证签名成功！");
        } else {
            log.error("验证签名失败！");
        }
        return result;
    }

    /**
     * 加密
     * 1.构造密钥生成器
     * 2.根据ecnodeRules规则初始化密钥生成器
     * 3.产生密钥
     * 4.创建和初始化密码器
     * 5.内容加密
     * 6.返回字符串
     **/
    public static String AESEncode(String appSecret, String content) throws Exception {
        try {
            if (StringUtils.isNotBlank(appSecret) && appSecret.length() < 16) {
                appSecret = appSecret + appSecret;
            }
            Cipher cipher = Cipher.getInstance("AES/CBC/NoPadding");
            int blockSize = cipher.getBlockSize();

            byte[] dataBytes = content.getBytes();
            int plaintextLength = dataBytes.length;
            if (plaintextLength % blockSize != 0) {
                plaintextLength = plaintextLength + (blockSize - (plaintextLength % blockSize));
            }

            byte[] plaintext = new byte[plaintextLength];
            System.arraycopy(dataBytes, 0, plaintext, 0, dataBytes.length);
            SecretKeySpec keySpec = new SecretKeySpec(appSecret.getBytes(), "AES");
            IvParameterSpec ivSpec = new IvParameterSpec(appSecret.getBytes());

            cipher.init(Cipher.ENCRYPT_MODE, keySpec, ivSpec);
            byte[] encrypted = cipher.doFinal(plaintext);

            return new sun.misc.BASE64Encoder().encode(encrypted);
        } catch (Exception e) {
            log.error("AESEncode error ", e);
            throw e;
        }
    }

    /**
     * 解密
     * @param appSecret
     * @param content
     * @return
     * @throws Exception
     */
	public static String AESDecode(String appSecret, String content) throws Exception {
        try {
            byte[] data = new sun.misc.BASE64Decoder().decodeBuffer(content);
            if (StringUtils.isNotBlank(appSecret) && appSecret.length() < 16) {
                appSecret = appSecret + appSecret;
            }
            Cipher cipher = Cipher.getInstance("AES/CBC/NoPadding");
            SecretKeySpec keySpec = new SecretKeySpec(appSecret.getBytes(), "AES");
            IvParameterSpec ivSpec = new IvParameterSpec(appSecret.getBytes());
            cipher.init(Cipher.DECRYPT_MODE, keySpec, ivSpec);
            byte[] encrypted = cipher.doFinal(data);
            return new String(encrypted);
        } catch (Exception e) {
            log.error("AESDecode error ", e);
            throw e;
        }
    }
}
